﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Isuka.Linq;

namespace Isuka.Reflection
{
    public static class TypeHelper
    {
        private static Dictionary<Type, Type> ienumCache
           = new Dictionary<Type, Type>();


        public static CustomAttributeBuilder CreateAttributeBuilder<TA>(object ctor = null, object fields = null, object properties = null)
            where TA : Attribute
        {
            Contract.Ensures(Contract.Result<CustomAttributeBuilder>() != null);

            var attrType = typeof(TA);

            var ct = new Type[] { };
            var cv = new object[] { };
            if (ctor != null)
            {
                var ctorInfo = new AnonymousTypeHelper(ctor);
                ct = ctorInfo.Properties.Select(x => x.PropertyType).ToArray();
                cv = ctorInfo.GetAllValues();
            }
            var con = typeof(TA).GetConstructor(ct);

            var ft = new FieldInfo[] { };
            var fv = new object[] { };
            if (fields != null)
            {
                var fieldsInfo = new AnonymousTypeHelper(fields);
                ft = fieldsInfo.Properties.Select(x => attrType.GetField(x.Name)).ToArray();
                fv = fieldsInfo.GetAllValues();
            }

            var pt = new PropertyInfo[] { };
            var pv = new object[] { };
            if (properties != null)
            {
                var propertiesInfo = new AnonymousTypeHelper(properties);
                pt = propertiesInfo.Properties.Select(x => attrType.GetProperty(x.Name)).ToArray();
                pv = propertiesInfo.GetAllValues();
            }

            return new CustomAttributeBuilder(con, cv, pt, pv, ft, fv);
        }


        /// <summary>
        /// seqType が実装する IEnumerable(Of T) を検索し、最初に見つかった型を返す
        /// </summary>
        /// <param name="seqType"></param>
        /// <returns></returns>
        public static Type FindIEnumerableT(Type seqType)
        {
            if (ienumCache.ContainsKey(seqType))
            {
                return ienumCache[seqType];
            }
            else
            {
                var r = FindIEnumerableTCore(seqType);
                ienumCache.Add(seqType, r);
                return r;
            }
        }

        [ContractVerification(false)]
        static Type FindIEnumerableTCore(Type seqType)
        {
            if (seqType == null || seqType == typeof(string))
            {
                return null;
            }
            else if (seqType.IsArray)
            {
                return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType());
            }
            else if (seqType.IsGenericType)
            {
                var found
                    = seqType.GetGenericArguments()
                        .SelectFirstOrDefault(x => typeof(IEnumerable<>).MakeGenericType(x)
                                            , y => y.IsAssignableFrom(seqType));

                if (found != null)
                {
                    return found;
                }
            }

            /// 継承関係のチェック
            return seqType.GetInterfaces()
                    .SelectFirstAsEnumerable(x => FindIEnumerableTCore(x), y => y != null)
                    .DefaultIfEmpty(() =>
                    {
                        if (seqType.BaseType != null && seqType.BaseType != typeof(object))
                        {
                            return FindIEnumerableTCore(seqType.BaseType);
                        }
                        else
                        {
                            return null;
                        }
                    }).First();
        }

        /// <summary>
        /// 型Tの戻り値を取得する
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static object GetDefaultOf(Type type)
        {
            Contract.Requires<ArgumentNullException>(type != null, "type");
            var defexpr = Expression.Default(type);
            Contract.Assume(defexpr != null);
            var expr = Expression.Lambda(defexpr);
            return expr.Compile().DynamicInvoke();
        }

        /// <summary>
        /// seqType が列挙型の場合、値の型を返す。
        /// そうでない場合、seqType そのものを返す。
        /// </summary>
        /// <param name="seqType"></param>
        /// <returns></returns>
        public static Type GetElementType(Type seqType)
        {
            var r = FindIEnumerableT(seqType);
            if (r != null)
            {
                return r.GetGenericArguments()[0];
            }
            else
            {
                return seqType;
            }
        }
    }
}
